package jfae.core.properties.parsers;

import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jfae.core.annotations.EditableBean;
import jfae.core.annotations.PropPresentation;
import jfae.core.properties.FieldInfo;
import jfae.core.properties.parsers.annotation.AnnotationHandler;
import jfae.core.properties.parsers.annotation.PresentationHandler;

/**
 * Class level configuration parser for classes annotated with {@link EditableBean} 
 * @author bobpuley
 *
 */
public class ClassPropertyHandler extends FieldPropertyHandler{
	
	private PresentationHandler presHandler = new PresentationHandler();
	private Class<?> beanClass;
	private Set<Class<?>> exclusionTypes = new HashSet<Class<?>>();
	private Set<String> exclusionNames = new HashSet<String>();
	private List<String> reorderProperties = new ArrayList<String>();
	private Map<String, PropPresentation> presentations = new HashMap<String, PropPresentation>();

	public ClassPropertyHandler(List<AnnotationHandler<?>> annHandlers) {
		super(annHandlers);
	}

	public boolean accept(PropertyDescriptor prop, BeanInfo info){
		setup(info.getBeanDescriptor().getBeanClass());
		if(beanClass != null && beanClass.isAnnotationPresent(EditableBean.class)){
			return prop.getWriteMethod() != null && 
					!exclusionNames.contains(prop.getName()) && 
					!exclusionTypes.contains(prop.getPropertyType());//TODO: fare in modo che gestisca le sottoclassi (isAssignableFrom)
		}
		return false;	
	}
	
	public FieldInfo<?> parse(PropertyDescriptor prop){
		FieldInfo<?> field = null;

		Class<?> type = prop.getPropertyType();
		Method readMethod = prop.getReadMethod();
		
		field = buildFieldInfo(prop, type);
		// First apply class level rules
		applyRules(field);
		// then apply annotations applied at method level
		applyAnnotations(field, readMethod);

		return field;
	}

	private void applyRules(FieldInfo<?> field) {
		String name = field.getName();
		int position = reorderProperties.indexOf(name);
		if(position >= 0){
			field.setOrder(position);
		}
		if(presentations.containsKey(name)){
			PropPresentation propPresentation = presentations.get(name);
			field.setOrder(propPresentation.order());
			presHandler.handle(propPresentation.presentation(), field);
		}
	}

	private void setup(Class<?> beanClass) {
		if(this.beanClass == null || this.beanClass != beanClass){
			this.beanClass = beanClass;
			if(beanClass.isAnnotationPresent(EditableBean.class)){
				EditableBean annotation = beanClass.getAnnotation(EditableBean.class);
				exclusionTypes = new HashSet<Class<?>>(Arrays.asList(annotation.exclusionTypes()));
				exclusionNames = new HashSet<String>(Arrays.asList(annotation.exclusionNames()));
				reorderProperties = new ArrayList<String>(Arrays.asList(annotation.reorderProperties()));
				presentations = new HashMap<String, PropPresentation>();
				PropPresentation[] propsPresentation = annotation.propsPresentation();
				for (PropPresentation item : propsPresentation) {
					presentations.put(item.name(), item);
				}
			}
		}
	}

}
